/** ***************************************************************************
 * CoffeeZip.cpp
 * 2012 CoffeeParty Ltd.
 * Date Created: 2012-07-21                                  format YYYY-MM-DD
 * Revision Id: $Id$
 ******************************************************************************
 */

#include "CoffeeCommons.hpp"
#include "CoffeeLogger.hpp"
#include "CoffeeIO.hpp"

#include "CoffeeZip.hpp"

CoffeeZip::CoffeeZip()
{
}

CoffeeZip::~CoffeeZip()
{
    if (qptr)
        drop(qptr);
}

void CoffeeZip::testCompress()
{
//    unsigned char *compr, *uncompr;
//    unsigned long comprlen, uncomprlen;
//
//    const char teststring[] = "hello world!";
//    unsigned long length = (unsigned long)strlen(teststring) + 1;
//
//    int error = -1;
//    error = compress(compr, &comprlen, (const unsigned char*)teststring, length);
//
//    strcpy((char*)uncompr, "garbage");
//
//    error = uncompress(uncompr, &uncomprlen, compr, comprlen);
//    if (strcmp((char*)uncompr, teststring))
//        c_print("Uncompress Error");
//    else c_print("Uncompress Success");
}

void CoffeeZip::testDeflate()
{
//    unsigned char *compr;
//    unsigned long comprlen;
//
//    z_stream com_stream;
//    int error = -1;
//    const char teststring[] = "hello world!";
//    unsigned long length = (unsigned long)strlen(teststring) + 1;
//
//    com_stream.zalloc = (alloc_func)0;
//    com_stream.zfree = (free_func)0;
//    com_stream.opaque = (voidpf)0;
//
//    error = deflateInit(&com_stream, Z_DEFAULT_COMPRESSION);
//    c_print("DeflateInit");
//
//    com_stream.next_in = (unsigned char*)teststring;
//    com_stream.next_out = compr;
//
//    while (com_stream.total_in != length && com_stream.total_out < comprlen)
//    {
//        com_stream.avail_in = com_stream.avail_out = 1;
//        error = deflate(&com_stream, Z_NO_FLUSH);
//        c_print("Deflate");
//    }
//
//    for (;;)
//    {
//        com_stream.avail_out = 1;
//        error = deflate(&com_stream, Z_FINISH);
//        if (error == Z_STREAM_END)
//            break;
//        c_print("Deflate");
//    }
//
//    error = deflateEnd(&com_stream);
//    c_print("DeflateEnd");
}

void CoffeeZip::testInflate()
{
//    unsigned char *compr, *uncompr;
//    unsigned long comprlen, uncomprlen;
//
//    const char teststring[] = "hello world!";
//
//    int error = -1;
//    z_stream dec_stream;
//
//    strcpy((char*)uncompr, "garbage");
//    dec_stream.zalloc = (alloc_func)0;
//    dec_stream.zfree = (free_func)0;
//    dec_stream.opaque = (voidpf)0;
//    dec_stream.next_in = compr;
//    dec_stream.avail_in = 0;
//    dec_stream.next_out = uncompr;
//
//    error = inflateInit(&dec_stream);
//    while (dec_stream.total_out < uncomprlen && dec_stream.total_in < comprlen)
//    {
//        dec_stream.avail_in = dec_stream.avail_out = 1;
//        error = inflate(&dec_stream, Z_NO_FLUSH);
//        if (error == Z_STREAM_END)
//            break;
//        c_print("Inflate");
//    }
//
//    error = inflateEnd(&dec_stream);
//    c_print("InflateEnd");
//
//    if (strcmp((char*)uncompr, teststring))
//        c_print("Inflate Error");
//    else c_print("Inflate Success");
}

bool CoffeeZip::open(const char *filename)
{
    CoffeeFile *qptr = new CoffeeFile(filename);
    if (!qptr)
        return false;

    unsigned short signature;
    qptr->seek(0);
    qptr->read((char*)&signature, 2);
    qptr->seek(0);

    if (signature == 0x8b1f) //!< This is a GZip Header
    {
        if (qptr->read((char*)&header, sizeof(SI_GZIPHeader)) == sizeof(SI_GZIPHeader))
        {
            if (header.signature != 0x8b1f)
                return false;

            if (header.flags & GZ_EX_FIELDS)
            {
                unsigned short datalen;
                qptr->read((char*)&datalen, 2);
                qptr->seek(datalen, true);
            }

            char filename[0xFF];
            if (header.flags & GZ_FILENAME)
            {
                char c;
                qptr->read(&c, 1);
                while (c)
                {
                    strcat(filename, &c);
                    qptr->read(&c, 1);
                }
            }

            if (header.flags & GZ_COMMENT)
            {
                char c = 'a';
                while (c)
                    qptr->read(&c, 1);
            }

            if (header.flags & GZ_CRC16)
                qptr->seek(2, true);

            //! char filename_length = strlen(filename);
            int compress_method = header.compressMethod;
            int offset = qptr->pos();
            int compress_size = (qptr->size() - 8) - qptr->pos();
            int uncompress_size;
            int gz_crc32;

            qptr->seek(compress_size, true);
            qptr->read((char*)&gz_crc32, 4);
            qptr->read((char*)&uncompress_size, 4);
            inflateStream(offset, uncompress_size, compress_size, compress_method);
        }
    }
    else
        return false;
    return true;
}

void CoffeeZip::inflateStream(size_t offset, size_t uncompress_size, size_t compress_size, int compress_method)
{
    unsigned char *decrypted_buffer = 0;
    unsigned int decrypted_size = compress_size;

    char *ptrbuffer = new char[uncompress_size];
    if (!ptrbuffer)
    {
        c_print("Not enough memory for decompressing");
        return;
    }

    unsigned char *data = decrypted_buffer;
    if (!data)
    {
        data = new unsigned char[decrypted_size];
        if (!data)
        {
            c_print("Not enough memory for decompression");
            delete[] ptrbuffer;
            return;
        }

        qptr->seek(offset);
        qptr->read((char*)data, decrypted_size);
    }

    z_stream stream;
    int error;

    stream.next_in = (Bytef*)data;
    stream.avail_in = (uInt)decrypted_size;
    stream.next_out = (Bytef*)ptrbuffer;
    stream.avail_out = uncompress_size;
    stream.zalloc = (alloc_func)0;
    stream.zfree = (free_func)0;

    error = inflateInit2(&stream, -MAX_WBITS);
    if (error == Z_OK)
    {
        error = inflate(&stream, Z_FINISH);
        inflateEnd(&stream);
        if (error == Z_STREAM_END)
            error = Z_OK;
        error = Z_OK;
        inflateEnd(&stream);
    }

    delete[] data;

    if (error != Z_OK)
    {
        c_print("Error Decompressing");
        delete[] ptrbuffer;
    }
}

void CoffeeZip::checkError(int n, int line)
{
    switch(n)
    {
    case Z_ERRNO:
        c_print(line, "Error Reading/Writing");
        break;
    case Z_STREAM_ERROR:
        c_print(line, "Invalid Compression Level");
        break;
    case Z_DATA_ERROR:
        c_print(line, "Invalid or Incomplete Deflate Data");
        break;
    case Z_MEM_ERROR:
        c_print(line, "Out Of Memory");
        break;
    case Z_VERSION_ERROR:
        c_print(line, "zlib Version Mismatch");
        break;
    }
}

